package ch.zhaw.cctd.domain;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.InputStream;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zhaw.cctd.domain.match.MatchClient;
import ch.zhaw.cctd.domain.match.MatchServer;
import ch.zhaw.cctd.domain.player.RessourceException;
import ch.zhaw.cctd.domain.prototype.Prototype;
import ch.zhaw.cctd.domain.prototype.PrototypeList;
import ch.zhaw.cctd.domain.prototype.TowerPrototype;
import ch.zhaw.cctd.domain.tower.TowerBuildException;
import ch.zhaw.cctd.logic.event.ChatEventListener;
import ch.zhaw.cctd.logic.event.EventHandler;
import ch.zhaw.cctd.logic.event.EventHandlerImpl;
import ch.zhaw.cctd.logic.event.LobbyEventListener;
import ch.zhaw.cctd.logic.network.NetworkException;
import ch.zhaw.cctd.logic.network.Networking;
import ch.zhaw.cctd.logic.network.RemoteServer;
import ch.zhaw.cctd.logic.xml.XmlReadException;

/**
 * Bietet eine simple Aufbau- und Zugriffsschnittstelle für die Domain an. Ist
 * zuständig dafür die erstellte Domain am Netzwerk anzumelden und korrekt an
 * der RMI Registry zu registrieren. Sie bietet methoden zum Zugriff auf einen
 * Laufenden Server und dessen Zustand.
 * 
 * @author Rolf Koch
 * @author Benjamin Felder
 */
public final class DomainFacade {

	private static final Logger logger = LoggerFactory.getLogger(MatchClient.class);

	private static Networking networking;
	private static RemoteServer matchServer;
	private static MatchClient matchClient;

	/**
	 * Register a Client chateventlistener on the Server Eventhandler
	 * 
	 * @param listener
	 * @throws NetworkException
	 */
	public static void registerChatEventListener(ChatEventListener listener) throws NetworkException {
		try {
			DomainFacade.matchClient.getEventHandler().addChatEventListener(listener);
		} catch (ConnectException e) {
			throw new NetworkException("lost connection to server", e, true);
		} catch (RemoteException e) {
			logger.error("Network Error, could not add ChatEventlistener to Eventhandler", e);
		}
	}

	/**
	 * Unregister a Client chateventlistener on the Server Eventhandler
	 * 
	 * @param listener
	 * @throws NetworkException
	 */
	public static void removeChatEventListener(ChatEventListener listener) throws NetworkException {
		try {
			DomainFacade.matchClient.getEventHandler().removeChatEventListener(listener);
		} catch (ConnectException e) {
			throw new NetworkException("lost connection to server", e, true);
		} catch (RemoteException e) {
			logger.error("Network Error, could not remove the ChatEventlistener from the Eventhandler", e);
		}

	}

	/**
	 * Sends a Chat Message as a ChatEvent.
	 * 
	 * @param chatMessageText
	 * @return true if successful, false on error
	 */
	public static boolean sendChatMessage(String chatMessageText) {
		if (chatMessageText == null || chatMessageText.isEmpty())
			return true; // ignore empty messages
		try {
			DomainFacade.matchClient.getEventHandler().dispatchChatMessage(DomainFacade.matchClient.getLocalPlayer(), chatMessageText);
			return true;
		} catch (RemoteException e) {
			logger.warn("Server connection lost", e);
			return false;
		}
	}

	/**
	 * Register a Client LobbyEventListener on the Server Eventhandler
	 * 
	 * @param listener
	 * @throws NetworkException
	 */
	public static void registerLobbyEventListener(LobbyEventListener listener) throws NetworkException {
		try {
			DomainFacade.matchClient.getEventHandler().addLobbyEventListener(listener);
		} catch (ConnectException e) {
			throw new NetworkException("lost connection to server", e, true);
		} catch (RemoteException e) {
			logger.error("Network Error, could not add LobbyEventListener to Eventhandler", e);
		}
	}

	/**
	 * Unregister a Client LobbyEventListener on the Server EventHandler
	 * 
	 * @param listener
	 * @throws NetworkException
	 */
	public static void removeLobbyEventListener(LobbyEventListener listener) throws NetworkException {
		try {
			DomainFacade.matchClient.getEventHandler().removeLobbyEventListener(listener);
		} catch (ConnectException e) {
			throw new NetworkException("lost connection to server", e, true);
		} catch (RemoteException e) {
			logger.error("Network Error, could not remove the LobbyEventListener from the Eventhandler", e);
		}

	}

	/**
	 * LobbyAction: informs all other users, that the local user is ready.
	 * @return	true if successful, false on error
	 */
	public static boolean lobbySendUserIsReady() {
		try {
			DomainFacade.matchClient.getEventHandler().dispatchLobbyReady(DomainFacade.matchClient.getLocalPlayer());
			return true;
		} catch (RemoteException e) {
			logger.warn("Server connection lost", e);
			return false;
		}
	}
	
	public static boolean lobbySendUserIsNotReady() {
		try {
			DomainFacade.matchClient.getEventHandler().dispatchLobbyUnready(DomainFacade.matchClient.getLocalPlayer());
			return true;
		} catch (RemoteException e) {
			logger.warn("Server connection lost", e);
			return false;
		}
	}
	
	/**
	 * LobbyAction: informs all other users, that the game starts now
	 * @return	true if successful, false on error
	 */
	public static boolean lobbySendGameStart() {
		try {
			DomainFacade.startMatch();
			DomainFacade.matchClient.getEventHandler().dispatchLobbyGameStart(DomainFacade.matchClient.getLocalPlayer());

			return true;
		} catch (RemoteException e) {
			logger.warn("Server connection lost", e);
			return false;
		}
	}

	/**
	 * Creates a new Server on the local Client including the RMI Registry
	 * 
	 * @param xmlinput
	 * @param port
	 * @return RemoteServer
	 * @throws XmlReadException
	 * @throws NetworkException
	 */
	public static RemoteServer startHostedGame(InputStream xmlinput, int port) throws XmlReadException, NetworkException {
		if (DomainFacade.networking == null)
			DomainFacade.networking = new Networking();
		try {
			logger.trace("Host new Game");
			DomainFacade.matchServer = new MatchServer(new EventHandlerImpl(), xmlinput);
		} catch (RemoteException e) {
			logger.error("Match Server could not be created", e);
		}

		// Host the Game on RMI
		DomainFacade.networking.hostGame(DomainFacade.matchServer);

		return DomainFacade.matchServer;
	}

	/**
	 * Creates a Server with the default Port
	 * 
	 * @param xmlinput
	 * @throws XmlReadException
	 * @throws NetworkException
	 */
	public static void startHostedGame(InputStream xmlinput) throws XmlReadException, NetworkException {
		DomainFacade.startHostedGame(xmlinput, Networking.DEFAULT_PORT);
	}

	/**
	 * Joins a Remote Server and registers the local Client on the Server
	 * 
	 * @param addresse
	 * @param port
	 * @param localPlayerName
	 * @throws NetworkException
	 */
	public static void joinGame(String addresse, int port, String localPlayerName) throws NetworkException {
		if (DomainFacade.networking == null)
			DomainFacade.networking = new Networking();

		// Create new client
		try {
			logger.trace("Creating new Matchclient");
			DomainFacade.matchClient = new MatchClient();
			logger.trace("Export done");

			// Locate the Remote Server and Register the Client on RMI
			logger.trace("joining Game");
			DomainFacade.matchServer = DomainFacade.networking.joinGame(addresse, port);

			// Get the Eventhandler
			logger.trace("get Eventhandler");
			EventHandler eh = DomainFacade.matchServer.getEventHandler();

			// Get the PrototypeList
			logger.trace("get Prototypelist");
			PrototypeList ptl = DomainFacade.matchServer.getPrototypeList();

			// Register the Eventhandler, the Prototypelist and the Server on
			// the Local Client
			logger.trace("Set Eventhandler, Prototypelist and Server");
			DomainFacade.matchClient.initMatchClient(eh, ptl, DomainFacade.matchServer);
			logger.trace("new Client now Ready for play: {} {} ", eh.toString(), ptl.toString());

			// Register the client on the Server Side
			logger.trace("Register Client");
			int clientID = DomainFacade.matchServer.registerClient(DomainFacade.matchClient);

			// Set the Local Player ID
			logger.trace("Set Playerid: {}", clientID);
			DomainFacade.matchClient.setPlayerID(clientID);
			
			// Set the Local Player Name
			logger.trace("Set PlayerName: {}", localPlayerName);
			DomainFacade.matchClient.setPlayerName(localPlayerName);
			
			//Register Player
			DomainFacade.matchClient.registerPlayer();
			logger.trace("Client ready for Business");
			
			DomainFacade.matchClient.getEventHandler().dispatchLobbyJoined(DomainFacade.matchClient.getLocalPlayer());

		} catch (RemoteException e) {
			logger.error("Error while joining the Server", e);
		}
	}

	/**
	 * Join a remote Server using the Default Port
	 * 
	 * @param addresse
	 * @param localPlayerName
	 * @throws NetworkException
	 */
	public static void joinGame(String addresse, String localPlayerName) throws NetworkException {
		DomainFacade.joinGame(addresse, Networking.DEFAULT_PORT, localPlayerName);
	}

	/**
	 * Deregister the local Client from the Server and destroy the local RMI
	 * Registry settings. Delete the client and the Server locally
	 */
	public static void leaveGame() {
		logger.debug("Leaving Game");
		// Check if Networking is still possible and if the Server has already
		// been Connected before
		if (DomainFacade.networking != null && DomainFacade.matchServer != null && DomainFacade.matchClient != null) {
			try {
				DomainFacade.matchClient.getEventHandler().dispatchLobbyLeft(DomainFacade.matchClient.getLocalPlayer());
				// Unregister from Server
				logger.trace("Try to unregister Client");
				DomainFacade.matchServer.unregisterClient(DomainFacade.matchClient);
			} catch (RemoteException e) {
				// Server is already gone, cant unregister. Not really Critical.
				logger.trace("Uncritical RemoteException: Could not unregister Client, Server is not reachable ", e);
			}
			DomainFacade.networking.cleanup();
		}

		// Destroy the client and the server
		DomainFacade.matchClient = null;
		DomainFacade.matchServer = null;
	}

	/**
	 * Get the PrototypeList from the Remote Server
	 * 
	 * @return PrototypeList
	 */
	public static PrototypeList getPrototypeList() {
		return DomainFacade.matchClient.getPrototypeList();
	}

	/**
	 * Get a Prototype by Class and by its ID
	 * 
	 * @param clazz
	 * @param id
	 * @return clazz Prototype from ID
	 */
	public static <T extends Prototype> T getPrototype(Class<T> clazz, String id) {
		return DomainFacade.matchClient.getPrototype(clazz, id);
	}

	/**
	 * Quits the Game
	 */
	public static void quitGame() {
		logger.info("Match Quit");
		leaveGame();
	}

	/**
	 * Returns the Dimensions of the Map
	 * 
	 * @return
	 */
	public static Dimension getMapSize() {
		try {
			return DomainFacade.matchServer.getMapSize();
		} catch (RemoteException e) {
			logger.info("Server is not answering", e);
		}
		return null;
	}
	
	/**
	 * Returns the Player Hashmap
	 * 
	 * @return
	 */
	public static Map<Integer, String> getPlayerMap() {
		try {
			return DomainFacade.matchServer.getPlayerMap();
		} catch (RemoteException e) {
			logger.info("Server is not answering", e);
		}
		return null;
	}

	/**
	 * Execute the StartMatch Method. This has to be done at least Once to start
	 * the Server calculating the Match.
	 */
	private static void startMatch() {
		try {
			if (!DomainFacade.matchServer.isStarted()) {
				DomainFacade.matchServer.startMatch();
			}
		} catch (RemoteException e) {
			logger.error("Could not start the Match, Server unreachable", e);
		}

	}

	/**
	 * Returns all Static Map Objects from the Server
	 * @return static map objects
	 */
	public static List<? extends GraphicalElement> getStaticMapObjects() {
		try {
			return matchServer.getStaticMapObjects();
		} catch (RemoteException e) {
			logger.error("Server unreachable", e);
		}
		return null;
	}

	/**
	 * Returns all Towers in a List from the Server
	 * @return tower map objects
	 */
	public static List<? extends GraphicalElement> getTowersMapObjects() {
		try {
			return matchServer.getTowersMapObjects();
		} catch (RemoteException e) {
			logger.error("Server unreachable", e);
		}
		return null;
	}

	/**
	 * Returns all Movable Map Objects
	 * @return movable map objects 
	 */
	public static List<? extends GraphicalElement> getMovableMapObjects() {
		try {
			return matchServer.getMovableMapObjects();
		} catch (RemoteException e) {
			logger.error("Server unreachable", e);
		}
		return null;
	}
	
	/**
	 * Returns get Local Player Money
	 * @return int
	 */
	public static int getLocalPlayerMoney() {
		return matchClient.getLocalPlayerMoney();
	}
	
	/**
	 * Returns get creep count on the map
	 * @return int
	 */
	public static int getCreepCount() {
		try {
			return matchServer.getCreepCount();
		} catch (RemoteException e) {
			logger.error("Server unreachable", e);
		}
		return 0;
	}
	
	/**
	 * Returns get Wave Number
	 * @return int
	 */
	public static int getWaveNumber() {
		try {
			return matchServer.getWaveNumber();
		} catch (RemoteException e) {
			logger.error("Server unreachable", e);
		}
		return 0;
	}
	
	public static Point isValidTowerBuildLocation(Rectangle rect) {
		return matchClient.isValidTowerBuildLocation(rect);
	}
	
	public static void buildTower( TowerPrototype towerPrototype, Point point) throws RessourceException, TowerBuildException {
		matchClient.buildTower(towerPrototype, point);
	}

	
}
